/**
 * @File Name: webserver.cpp
 * @brief  WebServer 类 的实现
 * @Author : beyon_sir email:caoboyang.chn@gmail.com
 * @Version : 1.0
 * @Create Date : 2022-11-20
 * 
 */
#include "webserver.h"

using namespace std;

WebServer::WebServer(
            int port, int trigMode, int timeoutMS, bool OptLinger,
            int sqlPort, const char* sqlUser, const  char* sqlPwd,
            const char* dbName, int connPoolNum, int threadNum,
            bool openLog, int logLevel, int logQueSize):
            port_(port), openLinger_(OptLinger), timeoutMS_(timeoutMS), isClose_(false),
            timer_(new HeapTimer()), threadpool_(new ThreadPool(threadNum)), epoller_(new Epoller()) // 初始化timer_ threadpool_ epoller_
    {
    srcDir_ = getcwd(nullptr, 256);         // 获取当前工作路径(运行命令时所处的路径)
    assert(srcDir_);                        // 断言:计算expression的值为假,那么就向stderr打印一条出错信息，然后调用abort来终止程序；
    strncat(srcDir_, "/resources/", 16);    // 拼接字符串 当前工作路径 = 原工作路径 + /resources/

    HttpConn::userCount = 0;                // 当前连接的客户端总数
    HttpConn::srcDir = srcDir_;             // 设置资源的目录 

    SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum);

    // 初始化事件的模式
    InitEventMode_(trigMode);
    // 初始化socket
    if(!InitSocket_()) { isClose_ = true; }

    // 日志
    if(openLog) {
        Log::Instance()->init(logLevel, "./log", ".log", logQueSize);
        if(isClose_) { LOG_ERROR("========== Server init error!=========="); }
        else {
            LOG_INFO("========== Server init ==========");
            LOG_INFO("Port:%d, OpenLinger: %s", port_, OptLinger? "true":"false");
            LOG_INFO("Listen Mode: %s, OpenConn Mode: %s",
                            (listenEvent_ & EPOLLET ? "ET": "LT"),
                            (connEvent_ & EPOLLET ? "ET": "LT"));
            LOG_INFO("LogSys level: %d", logLevel);
            LOG_INFO("srcDir: %s", HttpConn::srcDir);
            LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum);
        }
    }
}

WebServer::~WebServer() {
    close(listenFd_);
    isClose_ = true;
    free(srcDir_);
    SqlConnPool::Instance()->ClosePool();
}

/// @brief 设置监听和客户端通信的文件描述符的模式
/// @param trigMode 
void WebServer::InitEventMode_(int trigMode) {
    listenEvent_ = EPOLLRDHUP;                  // 监听文件描述符: EPOLLRDHUP:检测对方是否正常关闭 即套接字关闭了连接, 或者关闭了正写一半的连接。
    connEvent_ = EPOLLONESHOT | EPOLLRDHUP;     // 连接客户端文件描述符: EPOLLONESHOT:内核最多触发其上注册的一个可读、可写或者异常事件，且只触发一次
    switch (trigMode)
    {
    case 0:
        break;
    case 1:
        connEvent_ |= EPOLLET;
        break;
    case 2:
        listenEvent_ |= EPOLLET;
        break;
    case 3:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    default:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }
    HttpConn::isET = (connEvent_ & EPOLLET);    // 判断当前客户端连接是否是ET模式(边沿触发)
}

/// @brief 主线程逻辑
void WebServer::Start() {
    int timeMS = -1;  /* epoll wait timeout == -1 无事件将阻塞 */
    if(!isClose_) { LOG_INFO("========== Server start =========="); }
    while(!isClose_) {
        // 超时连接
        if(timeoutMS_ > 0) {
            timeMS = timer_->GetNextTick();
        }
        // 循环调用EPOLL_wait 监听是否有相应事件到达 
        // 如果设置了timeoutMS_ 超时时间 那么我们调用timer_->GetNextTick() 
        // 获取当前连接的客户端中最早到达超时时间的时间 与当前时间的差值 作为epoll_wait的检测超时时间
        int eventCnt = epoller_->Wait(timeMS);
        for(int i = 0; i < eventCnt; i++) {
            /* 处理事件 */
            int fd = epoller_->GetEventFd(i);
            uint32_t events = epoller_->GetEvents(i);

            // 处理事件监听 接收客户端连接
            if(fd == listenFd_) {
                DealListen_();
            }
            // 处理连接错误 关闭通信文件描述符 关闭连接
            else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                assert(users_.count(fd) > 0);
                CloseConn_(&users_[fd]);
            }
            // 处理读事件 (HTTP Request)
            else if(events & EPOLLIN) {
                assert(users_.count(fd) > 0);
                DealRead_(&users_[fd]);
            }
            // 处理写事件 (发送HTTP Response)
            else if(events & EPOLLOUT) {
                assert(users_.count(fd) > 0);
                DealWrite_(&users_[fd]);
            } else {
                LOG_ERROR("Unexpected event");
            }
        }
    }
}

void WebServer::SendError_(int fd, const char*info) {
    assert(fd > 0);
    int ret = send(fd, info, strlen(info), 0);
    if(ret < 0) {
        LOG_WARN("send error to client[%d] error!", fd);
    }
    close(fd);
}

/// @brief 关闭客户端连接
/// @param client 
void WebServer::CloseConn_(HttpConn* client) {
    assert(client);
    LOG_INFO("Client[%d] quit!", client->GetFd());
    // 将该连接从EPOLLPULL中删除
    epoller_->DelFd(client->GetFd());

    client->Close();
}

// 添加客户端连接
void WebServer::AddClient_(int fd, sockaddr_in addr) {
    assert(fd > 0);

    users_[fd].init(fd, addr); // 在users_中写入新客户端相关信息 并进行初始化
    // init客户端连接超时检测
    if(timeoutMS_ > 0) {
        timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd]));
    }
    // 将新接受的客户端 添加到EPOLLPULL中 进行事件检测
    epoller_->AddFd(fd, EPOLLIN | connEvent_);
    SetFdNonblock(fd); // 设置非阻塞
    LOG_INFO("Client[%d] in!", users_[fd].GetFd());
}

/// @brief 处理监听文件描述符
void WebServer::DealListen_() {
    // 保存连接客户端的信息
    struct sockaddr_in addr; 
    socklen_t len = sizeof(addr);
    do {
        int fd = accept(listenFd_, (struct sockaddr *)&addr, &len);
        // 接收连接错误 以及ET模式下所有信息都以读取完毕后 退出循环
        if(fd <= 0) { return;}
        // 判断最大客户端数量限制 
        else if(HttpConn::userCount >= MAX_FD) {
            // 给客户端发送"Server busy!" 信息 之后关闭客户端连接
            SendError_(fd, "Server busy!");
            LOG_WARN("Clients is full!");
            return;
        }
        // 添加客户端
        AddClient_(fd, addr); 
    } while(listenEvent_ & EPOLLET); // 如果当前listenfd_ 为ET模式 就循环读取(一次性将所有等待连接的客户端都处理)
}

/// @brief 处理客户端读操作
/// @param client 
void WebServer::DealRead_(HttpConn* client) {
    assert(client);
    // 重置当前客户端对应超时时间(因为有新的数据接收)
    ExtentTime_(client);
    //  向线程池中的任务队列添加任务 子线程进行读操作
    threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client));
}

void WebServer::DealWrite_(HttpConn* client) {
    assert(client);
    // 重置当前客户端对应超时时间(因为有新的数据接收)
    ExtentTime_(client);
    //  向线程池中的任务队列添加任务 子线程进行写操作
    threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client));
}

void WebServer::ExtentTime_(HttpConn* client) {
    assert(client);
    if(timeoutMS_ > 0) { timer_->adjust(client->GetFd(), timeoutMS_); }
}

/// @brief 完成读操作 由子线程执行 
/// @param client 
void WebServer::OnRead_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int readErrno = 0;
    // 读取客户端的数据
    ret = client->read(&readErrno);
    // 如果ret == 0 and readErrno = EAGAIN 表示当前所有可读信息都已读取完毕
    // 否则读取错误
    if(ret <= 0 && readErrno != EAGAIN) {
        // 关闭客户端
        CloseConn_(client);
        return;
    }
    // 处理业务逻辑: HTTP Request请求解析 生成HTTP Response
    OnProcess(client);
}

void WebServer::OnProcess(HttpConn* client) {
    // 调用HttpConn 中的处理函数
    if(client->process()) {
        // 如果解析HTTP request 成功 
        // 将相应客户端fd的epoll监听事件修正为connEvent_ | EPOLLOUT(监听是否可写)
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
    } else {
        // 如果解析HTTP request 失败 
        // 将相应客户端fd的epoll监听事件重置为connEvent_ | EPOLLIN (监听是否有该客户端新的信息输入)
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN);
    }
}

/// @brief 向写缓冲区写数据(HTTP response) 子线程运行
/// @param client 
void WebServer::OnWrite_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int writeErrno = 0;
    // 向写缓冲区写数据
    ret = client->write(&writeErrno);
    if(client->ToWriteBytes() == 0) {
        /* 传输完成 */
        if(client->IsKeepAlive()) {
            OnProcess(client);
            return;
        }
    }
    else if(ret < 0) {
        if(writeErrno == EAGAIN) {
            /* 继续传输 */
            epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
            return;
        }
    }
    CloseConn_(client);
}

/// @brief 初始化listenFd
/// @return 
bool WebServer::InitSocket_() {
    int ret;
    struct sockaddr_in addr;
    // 判断端口号是否在合理范围内
    if(port_ > 65535 || port_ < 1024) {
        LOG_ERROR("Port:%d error!",  port_);
        return false;
    }
    addr.sin_family = AF_INET;                  // 协议族ipv4
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 绑定任何可以绑定的ip地址
    addr.sin_port = htons(port_);

    struct linger optLinger = { 0 };
    if(openLinger_) {
        /* 优雅关闭: 直到所剩数据发送完毕或超时 */
        optLinger.l_onoff = 1;
        optLinger.l_linger = 1;
    }

    // 创建监听文件描述符listenFd_
    listenFd_ = socket(AF_INET, SOCK_STREAM, 0);
    if(listenFd_ < 0) {
        LOG_ERROR("Create socket error!", port_);
        return false;
    }

    /* 优雅关闭: 直到所剩数据发送完毕或超时 */
    ret = setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger));
    if(ret < 0) {
        close(listenFd_);
        LOG_ERROR("Init linger error!", port_);
        return false;
    }

    int optval = 1;
    /* 端口复用 */
    /* 只有最后一个套接字会正常接收数据。 */
    ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int));
    if(ret == -1) {
        LOG_ERROR("set socket setsockopt error !");
        close(listenFd_);
        return false;
    }

    // 绑定listenFd_
    ret = bind(listenFd_, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0) {
        LOG_ERROR("Bind Port:%d error!", port_);
        close(listenFd_);
        return false;
    }

    // 监听listenFd_
    ret = listen(listenFd_, 6);
    if(ret < 0) {
        LOG_ERROR("Listen port:%d error!", port_);
        close(listenFd_);
        return false;
    }

    // 将listenFd_添加到epoller_事件集合中
    ret = epoller_->AddFd(listenFd_,  listenEvent_ | EPOLLIN);
    if(ret == 0) {
        LOG_ERROR("Add listen error!");
        close(listenFd_);
        return false;
    }

    // 设置listenFd_为非阻塞
    SetFdNonblock(listenFd_);
    LOG_INFO("Server port:%d", port_);
    return true;
}

/// @brief 设置文件描述符fd为非阻塞
/// @param fd 
/// @return 
int WebServer::SetFdNonblock(int fd) {
    assert(fd > 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
}


